//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_CORE_TTY_CSCREEN_H_
#define _BUOLA_CORE_TTY_CSCREEN_H_

#include <buola/tty/input.h>
#include <buola/container/ctrie.h>
#include <termios.h>
#include <deque>
#include <buola/utility/usignal.h>
#include <stack>

namespace buola { namespace tty {

class CScreen
{
    struct SState
    {
        struct termios mAttrs;
        EInputMode mInputMode;
        bool mKeypad;    
        bool mEcho;
        bool mAutowrap;
        bool mCursor;
    };
    
public:
    typedef CTrie<unsigned char,int> TKeyTrie;
    
    CScreen();
    ~CScreen();
    
private:
    void InitTerminfo();

    void SetAttrs(SState &pS);
    void WriteSeq(const char *pSeq);

    void AddKey(const char *pName,EKeyCode pCode,EKeyMod pMod=KEYMOD_NONE);
    void InitKeyTrie();

    void FillFIFO();
    char EmptyFIFO();
    void ClearFIFO();

    static void DispatchWINCH(int);
    
    void RestoreState(SState &pS);
    
public:
    TKeyTrie *GetKeyTrie()
    {
        if(!mKeyTrie) InitKeyTrie();
        return mKeyTrie.get();
    }

    void PushState();
    void PopState();

    void SetInputMode(EInputMode pMode);
    EInputMode GetInputMode()   {   return mCurrent.mInputMode;  }
    
    void SetKeypad(bool pOn);
    bool GetKeypad()    {   return mCurrent.mKeypad; }
    
    void SetEcho(bool pOn);
    bool GetEcho()      {   return mCurrent.mEcho;   }
    
    void SetAutowrap(bool pOn);
    bool GetAutowrap()  {   return mCurrent.mAutowrap;   }

    void SetCursor(bool pOn);
    bool GetCursor()    {   return mCurrent.mCursor; }
    
    std::pair<int,int> GetWinSize();
    
    int ReadRawChar();
    bool CanGetChar();
    int GetChar();
    wchar_t GetWChar();

    void Output(const std::wstring &pString);
    void Output(const std::string &pString);
    
    void SaveCursor();
    void RestoreCursor();

    void CarriageReturn();
    void CursorUp();
    void CursorDown();
    void CursorLeft();
    void CursorRight();
    void ClearToBOL();
    void ClearToEOL();
    void ClearToEOS();
    
    static CScreen *GetDefault()
    {
        if(!mDefaultScreen)
            mDefaultScreen.reset(new CScreen);
        return mDefaultScreen.get();
    }

    template<typename tFunc>
    static void WatchWinSize(const tFunc &pFunc)
    {
        mWINCHMutex=true;
        mWINCHSignal.Connect(pFunc);
        mWINCHMutex=false;
        while(mWINCHTriggered)
        {
            mWINCHTriggered=false;
            mWINCHMutex=true;
            mWINCHSignal();
            mWINCHMutex=false;
        }
    }

private:
    int mIn,mOut; //fds
    std::unique_ptr<TKeyTrie> mKeyTrie;
    std::deque<char> mFIFO;
    
    SState mCurrent;
    SState mOriginal;
    
    std::stack<SState> mStateStack;

    static std::unique_ptr<CScreen> mDefaultScreen;
    static USignal<void()> mWINCHSignal;
    static bool mWINCHMutex;
    static bool mWINCHTriggered;
};

/*namespace tty*/ } /*namespace buola*/ }

#endif
